Replace forwarded ref instances of useProvidedRefOrCreate with useMergedRefs#7644
Replace forwarded ref instances of useProvidedRefOrCreate with useMergedRefs#7644iansan5653 wants to merge 19 commits intomainfrom
useProvidedRefOrCreate with useMergedRefs#7644Conversation
|
|
422c4e4 to
006cc27
Compare
…se-provided-ref-or-create
|
🤖 Lint issues have been automatically fixed and committed to this PR. |
|
Skipping changeset because the public facing changes are covered by the changeset in #7638 |
There was a problem hiding this comment.
Pull request overview
This PR continues the ref-modernization work in @primer/react by deprecating useProvidedRefOrCreate (problematic with callback refs) and migrating most component usage to useMergedRefs, aligning the codebase with React 19–compatible ref patterns.
Changes:
- Replace
useProvidedRefOrCreateusages in many components with an internaluseRef+useMergedRefspattern to support callback refs and remove@ts-expect-errorref casts. - Update
AnchoredOverlayto acceptReact.Reftypes and useuseMergedRefsfor anchor/overlay refs. - Minor doc/test variable renames to reflect “merged” terminology.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/hooks/useMergedRefs.ts | Update examples to use mergedRef naming in docs. |
| packages/react/src/hooks/useFocusTrap.ts | Import reordering / minor formatting while retaining useProvidedRefOrCreate for hook-specific behavior. |
| packages/react/src/hooks/useAnchoredPosition.ts | Import reordering / minor formatting while retaining useProvidedRefOrCreate for hook-specific behavior. |
| packages/react/src/hooks/tests/useMergedRefs.test.tsx | Rename local variable from combinedRef to mergedRef. |
| packages/react/src/experimental/Tabs/Tabs.tsx | Migrate tablist ref handling to useRef + useMergedRefs and remove ts-expect-error. |
| packages/react/src/experimental/Tabs/Tabs.examples.stories.tsx | Remove ts-expect-error tied to non-nullable ref typing. |
| packages/react/src/experimental/SelectPanel2/SelectPanel.tsx | Migrate anchor ref wiring to useRef + useMergedRefs. |
| packages/react/src/deprecated/ActionMenu.tsx | Migrate anchored overlay anchor ref wiring to useRef + useMergedRefs. |
| packages/react/src/TooltipV2/Tooltip.tsx | Migrate trigger ref wiring to useRef + useMergedRefs; widen trigger ref typing. |
| packages/react/src/TextInput/TextInput.tsx | Migrate input ref wiring to useRef + useMergedRefs; remove ts-expect-error. |
| packages/react/src/SelectPanel/SelectPanel.tsx | Migrate anchor ref wiring to useRef + useMergedRefs. |
| packages/react/src/PageHeader/PageHeader.tsx | Migrate root ref handling to useRef + useMergedRefs; simplify TitleArea to use forwarded ref directly. |
| packages/react/src/FilteredActionList/FilteredActionList.tsx | Migrate scroll container + input refs to useRef + useMergedRefs; remove ts-expect-error. |
| packages/react/src/Dialog/Dialog.tsx | Migrate autoFocus button ref wiring to useRef + useMergedRefs; remove ts-expect-error. |
| packages/react/src/Checkbox/Checkbox.tsx | Migrate input ref wiring to useRef + useMergedRefs; remove ts-expect-error. |
| packages/react/src/ButtonGroup/ButtonGroup.tsx | Migrate container ref wiring to useRef + useMergedRefs; remove ts-expect-error. |
| packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx | Change anchorRef prop typing to React.Ref, merge overlay refs with useMergedRefs, remove custom assignRef. |
| packages/react/src/ActionMenu/ActionMenu.tsx | Migrate internal anchor ref handling to useRef + useMergedRefs for composable ActionMenu. |
| packages/react/src/ActionList/List.tsx | Migrate list ref wiring to useRef + useMergedRefs; remove ts-expect-error. |
Comments suppressed due to low confidence (1)
packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:221
AnchoredOverlaynow always uses an internalanchorRefforuseAnchoredPosition,returnFocusRef, andignoreClickRefs. WhenrenderAnchorisnull, that internal ref is never attached to any element, soanchorRef.currentstaysnulland the overlay will never position/show. In therenderAnchor: nullbranch, use the providedanchorRef(and keep that prop typed as aRefObject) for positioning/focus management, and only use the merged ref whenrenderAnchoris provided.
const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning')
const anchorRef = useRef<HTMLElement>(null)
const mergedRef = useMergedRefs(anchorRef, externalAnchorRef)
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
const mergedOverlayRef = useMergedRefs(updateOverlayRef, overlayProps?.ref)
const anchorId = useId(externalAnchorId)
const onClickOutside = useCallback(() => onClose?.('click-outside'), [onClose])
const onEscape = useCallback(() => onClose?.('escape'), [onClose])
const onAnchorKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (!event.defaultPrevented) {
if (!open && ['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(event.key)) {
onOpen?.('anchor-key-press', event)
event.preventDefault()
}
}
},
[open, onOpen],
)
const onAnchorClick = useCallback(
(event: React.MouseEvent<HTMLElement>) => {
if (event.defaultPrevented || event.button !== 0) {
return
}
if (!open) {
onOpen?.('anchor-click')
} else {
onClose?.('anchor-click')
}
},
[open, onOpen, onClose],
)
const positionChange = (position: AnchorPosition | undefined) => {
if (onPositionChange && position) {
onPositionChange({position})
}
}
const {position} = useAnchoredPosition(
{
anchorElementRef: anchorRef,
floatingElementRef: overlayRef,
useProvidedRefOrCreate and migrate everything except for hooksuseProvidedRefOrCreate with useMergedRefs
| // Only works in React 19. In React 18, the cleanup function will be ignored and the ref will get called with | ||
| // Callback refs only work in React 19+. In React 18, the ref will get called with | ||
| // `null` which will be passed to each ref as expected. | ||
| if (majorReactVersion <= 18) return |
There was a problem hiding this comment.
I was getting a test failure because of an extra call to console.error in React 18 tests. Things are working as expected but React warns when a cleanup function is returned from a callback ref in versions before 19, because it's trying to future-proof for 19 and not cause unexpected side effects.
To resolve the warning, I just inspect the version of React explicitly instead of relying on the implicit differences in behavior. When we drop 19 support we can remove this.
|
👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/16899 |
|
Integration test results from github/github-ui:
CI check runs linting, type checking, and unit tests. Check the workflow logs for specific failures. Need help? If you believe this failure is unrelated to your changes, please reach out to the Primer team for assistance. |
In #7638 I created a
useMergedRefshook and migrated all instances ofuseRefObjectAsForwardedRefto it.A similar pattern is using
useProvidedRefOrCreatefor this. It's typically used with a type cast and ats-expect-errorcomment (❗):This is obviously problematic. The type errors are trying to point out the problem with this pattern: it will break if consumers pass ref callbacks. Ref callbacks are likely to become more and more common as React 19 has introduced ref callback cleanup functions, making ref callbacks a viable alternative to effects.
A much better pattern is to be consistent and use the same approach as in #7638:
Note, however, that I did not deprecate
useProvidedRefOrCreateor remove all calls to it. There are still some valid use cases where refs can be passed in to refer to elements rendered outside the component. We most often see this in anchor refs. There are also some utility hooks that can create a ref internally or use a passed ref. These two cases are still valid; the case I'm targeting here is specifically around forwarded refs.useMergedRefsto be React 19 compatible and public + deprecateuseRefObjectAsForwardedRefanduseProvidedRefOrCreate#7672Changelog
New
Changed
Removed
Rollout strategy
Testing & Reviewing
Merge checklist